﻿using System;
using System.Collections.Generic;
using IQIGame.Onigao.Framework;
using UnityEngine;

namespace IQIGame.Onigao.Game
{
    /// <summary>
    /// 类名 : 枚举 - 列表单元选中
    /// 作者 : Canyon / 龚阳辉
    /// 日期 : 2024-05-17 18:45
    /// 修改 : 2024-08-23 11:26
    /// 功能 : 
    /// </summary>
    public enum UnitSelectType
    {
        [Tooltip("无")] None,

        [Tooltip("ExToggle选择模式")] ByExToggle,

        [Tooltip("ExButton选择模式")] ByExButton,
    }

    /// <summary>
    /// 类名 : 列表的Unit对象的选择逻辑
    /// 作者 : Canyon / 龚阳辉
    /// 日期 : 2024-08-22 14:26
    /// 功能 : 
    /// </summary>
    public class ListUnitSelect<T> where T : UIBaseNode
    {
        public struct UnitSelectData
        {
            public UnitSelectType selectedType;
            public Action<int, T> callSelected;
            public Action<int, T> callUnSelected;
            public int maxSelected;
            public Action callSelectionIsFullWhenSelectOther;
            public Func<int, bool> callCheckIsCanSelected;

            static readonly public UnitSelectData Default = new() { selectedType = UnitSelectType.None };
        }

        internal class UnitData
        {
            public object component;
            public int listIndex = -1;
        }

        private UnitSelectData currentData = UnitSelectData.Default;

        public UnitSelectType selectedType
        {
            get => currentData.selectedType;
            set
            {
                currentData.selectedType = value;
            }
        }

        private readonly List<int> selectedIndexs;

        public int selectedIndex
        {
            get
            {
                int result = this.selectedIndexs.Count > 0 ? this.selectedIndexs[^1] : -1;
                if (result == -1 && isSingleSelected && lastSelectedIndex != -1)
                {
                    AddIndex(lastSelectedIndex);
                    result = lastSelectedIndex;
                }

                return result;
            }
        }

        private int lastSelectedIndex = -1;
        private ListUnitMappingData<T> dataMapping;
        private bool isComplateInitedListUnits;
        private readonly Dictionary<GameObject, UnitData> dicGObj2UnitData;

        private bool isNoSelectedState => this.selectedType == UnitSelectType.None;
        private bool isSingleSelected => currentData.maxSelected <= 1;
        private bool isMultiSelected => currentData.maxSelected > 1;

        public ListUnitSelect(ListUnitMappingData<T> dataMapping)
        {
            this.dataMapping = dataMapping;

            int capacity = dataMapping.capacity;
            this.dicGObj2UnitData = new(capacity);
            this.selectedIndexs = new(capacity);
        }

        public ListUnitSelect<T> Initialize(UnitSelectData data)
        {
            this.currentData = data;
            return this;
        }

        public ListUnitSelect<T> SetMitiSelect(int maxSelected = 1, Action funcFullWhenSelectOther = null)
        {
            currentData.maxSelected = Math.Max(maxSelected, 0);
            currentData.callSelectionIsFullWhenSelectOther = funcFullWhenSelectOther;
            return this;
        }

        public void ReIniteListUnits()
        {
            this.isComplateInitedListUnits = false;
            this.lastSelectedIndex = -1;
            this.ClearIndexs();
        }

        public void ComplateIniteListUnits()
        {
            this.isComplateInitedListUnits = true;
        }

        public void Refresh(T tUnit, int index)
        {
            if (!this.RefreshData(tUnit, index))
                return;

            bool isSelected = this.IsSelected(index);
            this.SetSelectedState(tUnit.gameObject, isSelected);
        }

        private bool IsSelected(int index)
        {
            return this.selectedIndexs.Contains(index);
        }

        private object GetUnitComponet(GameObject go)
        {
            var unitData = GetUnitData(go);
            return unitData != null ? unitData.component : null;
        }

        private UnitData GetUnitData(GameObject go)
        {
            return dicGObj2UnitData.TryGetValue(go, out UnitData unitData) ? unitData : null;
        }

        private int GetUnitListIndex(Component uComponent)
        {
            return GetUnitListIndex(uComponent.gameObject);
        }

        private int GetUnitListIndex(GameObject go)
        {
            var unitData = GetUnitData(go);
            return unitData != null ? unitData.listIndex : -1;
        }

        private void SetSelectedState(GameObject go, bool isSelected)
        {
            var obj = this.GetUnitComponet(go);
            switch (obj)
            {
                case ExToggle tog:
                    tog.isOn = isSelected;
                    break;
                case ExButton btn:
                    btn.SetSelectState(isSelected);
                    break;
            }
        }

        private bool RefreshData(T tUnit, int index)
        {
            if (this.isNoSelectedState)
                return false;

            GameObject gobj = tUnit.gameObject;
            this.SetSelectedState(gobj, false);
            this.UpdateIndexAfterInit(gobj, index);
            return true;
        }

        private void UpdateIndexAfterInit(GameObject gobj, int index)
        {
            this.InitComponent(gobj);
            var obj = this.GetUnitData(gobj);
            if (obj != null)
                obj.listIndex = index;
            InitFirstSelectedIndex(gobj);
        }

        private void InitComponent(GameObject gobj)
        {
            if (this.dicGObj2UnitData.TryGetValue(gobj, out var unitData))
            {
                return;
            }

            unitData = new UnitData();
            this.dicGObj2UnitData.Add(gobj, unitData);

            switch (this.selectedType)
            {
                case UnitSelectType.ByExToggle:
                    var exTog = gobj.GetOrAdd<ExToggle>();
                    exTog.systemCallValChged = this.OnTogChged;
                    exTog.funcCheckIsCanClick = this.CheckIsCanClick;
                    unitData.component = exTog;
                    break;
                case UnitSelectType.ByExButton:
                    var exBtn = gobj.GetOrAdd<ExButton>();
                    exBtn.systemCallClick = this.OnBtnClick;
                    exBtn.funcCheckIsCanClick = this.CheckIsCanClick;
                    unitData.component = exBtn;
                    break;
            }
        }

        private void InitFirstSelectedIndex(GameObject gobj)
        {
            if (!isSingleSelected || this.selectedIndexs.Count > 0)
            {
                return;
            }

            var index = this.GetUnitListIndex(gobj);
            if (index != -1)
            {
                this.lastSelectedIndex = index;
                this.AddIndex(index);
            }
        }

        private bool CheckIsCanClick(GameObject gobj)
        {
            return this.CheckIsCanSelected(this.GetUnitListIndex(gobj));
        }

        private void OnTogChged(ExToggle tog)
        {
            if (this.isNoSelectedState || !this.isComplateInitedListUnits)
            {
                return;
            }

            var index = GetUnitListIndex(tog);
            bool isOn = tog.isOn;
            if (isOn)
            {
                if (this.isMultiSelected && this.IsSelectedMax())
                {
                    SetIsOnWithoutNotify(tog, false);
                    this.NotifySelectionIsFullWhenSelectOther();
                    return;
                }
                this.OnComponentCallSelected(index);
            }
            else
            {
                this.NotifyUnSelectedUnit(index);
            }
        }

        private void SetIsOnWithoutNotify(ExToggle tog, bool isOn)
        {
            if (tog == null)
                return;
            tog.SetIsOnWithoutNotify(isOn);
            tog.onValueChanged?.Invoke(isOn);
        }

        private bool CheckIsCanSelected(int index)
        {
            if (currentData.callCheckIsCanSelected != null)
                return currentData.callCheckIsCanSelected(index);
            return true;
        }

        private bool IsSelectedMax()
        {
            return currentData.maxSelected > 1 && currentData.maxSelected <= this.selectedIndexs.Count;
        }

        private void OnBtnClick(ExButton btn)
        {
            if (this.isNoSelectedState || !this.isComplateInitedListUnits)
            {
                return;
            }

            if (this.isSingleSelected)
            {
                this.DoSingleBtnCall(btn);
            }
            else
            {
                this.DoMultipleBtnCall(btn);
            }
        }

        private void DoSingleBtnCall(ExButton btn)
        {
            this.ChangeBtnSingleSelectedState(btn);
            this.OnComponentCallSelected(GetUnitListIndex(btn));
        }

        private void ChangeBtnSingleSelectedState(ExButton btn)
        {
            var goSelected = this.dataMapping.GetGameObject(this.selectedIndex);
            if (goSelected == null)
            {
                return; 
            }
            var lastUnitData = this.GetUnitData(goSelected);
            ExButton btnLast = lastUnitData.component as ExButton;
            if (btnLast != null && btnLast != btn)
            {
                btnLast.SetSelectState(false);
                this.NotifyUnSelectedUnit(lastUnitData.listIndex);
            }

            btn.SetSelectState(true);
        }

        private void DoMultipleBtnCall(ExButton btn)
        {
            bool isLastSelected = btn.isSelected;
            btn.SetSelectState(!isLastSelected);
            if (isLastSelected)
            {
                this.NotifyUnSelectedUnit(GetUnitListIndex(btn));
            }
            else
            {
                if (this.IsSelectedMax())
                {
                    btn.SetSelectState(isLastSelected);
                    this.NotifySelectionIsFullWhenSelectOther();
                    return;
                }

                this.OnComponentCallSelected(GetUnitListIndex(btn));
            }
        }

        private void OnComponentCallSelected(int index)
        {
            if (!this.isComplateInitedListUnits)
                return;
            this.lastSelectedIndex = index;
            this.NotifySelectedUnit(index);
        }

        private void RemoveIndex(int index)
        {
            this.selectedIndexs.Remove(index);
        }

        private void AddIndex(int index)
        {
            if (!this.selectedIndexs.Contains(index))
                this.selectedIndexs.Add(index);
        }

        private void ClearIndexs()
        {
            this.selectedIndexs.Clear();
        }

        private void AddSelectedIndex(int index, bool isSingleState)
        {
            if (index < 0)
                return;
            if (isSingleState)
            {
                this.ClearIndexs();
            }
            else
            {
                this.RemoveIndex(index);
            }

            this.AddIndex(index);
        }

        public void SetSelected(int index, bool isSelected)
        {
            if (this.isNoSelectedState)
            {
                this.ClearIndexs();
                return;
            }

            if (this.isSingleSelected && isSelected)
                this.ChangeSelectState(this.selectedIndex, false);

            if (index >= 0)
            {
                this.ChangeSelectState(index, isSelected);
            }

            if (this.selectedIndex == -1)
            {
                this.NotifySelectedUnit(-1);
            }
        }

        public void SetSelected(GameObject gameObject, bool isSelected)
        {
            int index = this.GetUnitListIndex(gameObject);
            if (index != -1)
                this.SetSelected(index, isSelected);
        }

        private void ChangeSelectState(int index, bool isSelected)
        {
            var gobj = this.dataMapping.GetGameObject(index);
            if (gobj == null)
                return;

            bool isCanCall = this.CheckIsCanCall(gobj, isSelected);
            this.SetSelectedState(gobj, isSelected);

            if (!isCanCall)
                return;

            if (isSelected)
                this.NotifySelectedUnit(index);
            else
                this.NotifyUnSelectedUnit(index);
        }

        private bool CheckIsCanCall(GameObject go, bool isSelected)
        {
            bool isCanCall = false;
            var obj = this.GetUnitComponet(go);
            switch (obj)
            {
                case ExToggle tog:
                    isCanCall = (isSelected && tog.isOn) || (!isSelected && !tog.isOn);
                    break;
                case ExButton:
                    isCanCall = true;
                    break;
            }

            return isCanCall;
        }

        private void NotifySelectedUnit(int index)
        {
            this.AddSelectedIndex(index, this.isSingleSelected);
            currentData.callSelected?.Invoke(index, GetTEntity(index));
        }

        private T GetTEntity(int index)
        {
            return (index >= 0) ? this.dataMapping.GetTEntity(index) : null;
        }

        private void NotifyUnSelectedUnit(int index)
        {
            this.RemoveIndex(index);
            currentData.callUnSelected?.Invoke(index, GetTEntity(index));
        }

        private void NotifySelectionIsFullWhenSelectOther()
        {
            currentData.callSelectionIsFullWhenSelectOther?.Invoke();
        }

        public void Clear()
        {
            this.currentData = UnitSelectData.Default;
            this.isComplateInitedListUnits = false;
            this.dataMapping = null;
            this.lastSelectedIndex = -1;

            this.ClearIndexs();
            this.dicGObj2UnitData.Clear();
        }
    }
}
